package stencil.core.utils 
{
	import adobe.utils.CustomActions;
	import flash.events.EventDispatcher;
	import flash.utils.Timer;
	import flash.events.TimerEvent;
	import stencil.core.errors.ArgumentIsNullError;
	
	/**
	 * ...
	 * @author 
	 */
	public class TimerTask extends EventDispatcher {
		
		private static const WHILE:int = 0;
	
		private static const UNTIL:int = 1;
		
		private static const UNTIL_NOT:int = 1;
	
		private static const REPEAT:int = 2;
		
		private var _timer:Timer;
		
		private var _task:Function;
		
		private var _delay:Number;
		
		private var _condition:Function;
		
		private var _repeatCount:int;
		
		private var _mode:int = 0;
		
		private var _stopCondition:Function;
		
		private var _stopped:Boolean = false;
		
		//----------------------------------------------------------------
		// Constructor
		//----------------------------------------------------------------
		
		/**
		 * Creates a new instance of TimerTask.
		 * 
		 * @param	task	the task to be executed.
		 * @param	delay	the delay between task calls in milliseconds.
		 */
		public function TimerTask(task:Function, delay:Number) {
			this._task = task;
			this._delay = delay;
		}
		
		//----------------------------------------------------------------
		// Public methods
		//----------------------------------------------------------------
		
		/**
		 * Executes the task while the specified condition is satisfied. while(condition){ task }
		 * Interrupts currently executed task if any.
		 * 
		 * @param	condition	the condition.
		 */
		public final function executeWhile(condition:Function):void {
			this._repeatCount = 0;
			this.executeCall(WHILE, condition, true);
		}
		
		/**
		 * Executes the task until the specified condition is met. do{ task; while(!condition)}. 
		 * Interrupts currently executed task if any.
		 * 
		 * @param	condition
		 */
		public final function executeUntil(condition:Function):void {
			this._repeatCount = 0;
			this.executeCall(UNTIL, condition, true);
		}
		
		/**
		 * Executes the task until the specified condition is not met. do{ task; while(condition)}. 
		 * Interrupts currently executed task if any.
		 * 
		 * @param	condition
		 */
		public final function executeUntilNot(condition:Function):void {
			this._repeatCount = 0;
			this.executeCall(UNTIL_NOT, condition, true);
		}
		
		/**
		 * Executes the task 'repeatCount' times. 
		 * Interrupts currently executed task if any.
		 * 
		 * @param	repeatCount	specifies how many times the task should be executed. If 'repeatCount' is zero execution repeats infinitely.
		 */
		public final function executeRepeat(repeatCount:int = 0):void {
			this._repeatCount = repeatCount;
			this.executeCall(REPEAT, null);
		}
		
		/**
		 * Stops currently executed task if any.
		 */
		public final function stop():void {
			if (this._timer) {
				this._timer.removeEventListener(TimerEvent.TIMER, this.timerHandler);
				this._timer.removeEventListener(TimerEvent.TIMER_COMPLETE, this.timerCompleteHandler);
				this._timer.stop();
				this._timer = null;
				this._stopped = true;
				this.stopped();
			}
		}
		
		/**
		 * Stops the task when the specified condition is sotisfied. If no condition is given stops task if any.
		 * 
		 * @param	condition	the condition
		 */
		public final function stopWhen(condition:Function):void {
			if (this._condition == null) {
				this.stop();
			}else {
				if (this._timer) {
					this._stopCondition = condition;
				}
			}
		}
		
		//----------------------------------------------------------------
		// Private methods
		//----------------------------------------------------------------
		
		private function executeCall(mode:int, condition:Function = null, conditionIsRequred:Boolean = false):void {
			if (condition == null && conditionIsRequred) {
				throw new ArgumentError("`condition` argument is required.");
			}
			
			this.stop();
			
			this._mode = mode;
			this._condition = condition;
			this._stopped = false;
			this._timer = new Timer(this._delay, this._repeatCount);
			this._timer.addEventListener(TimerEvent.TIMER, this.timerHandler);
			this._timer.addEventListener(TimerEvent.TIMER_COMPLETE, this.timerCompleteHandler);
			this._timer.start();
		}
		
		private function timerHandler(event:TimerEvent):void {
			this.executeTask();
			this.dispatchEvent(event);
		}
		
		private function executeTask():void {
			try {
				
				if (this._stopCondition != null && this._stopCondition()) {
					this.stop();
					return;
				}
				
				switch(this._mode) {
					case WHILE:
						if (!this._stopped && this._condition()) {
							this._task.call(null);
						}else {
							this.stop();
							return;
						}
						break;
					case UNTIL:
						
						this._task.call(null);
						
						if (!this._stopped && this._condition()) {
							this.stop();
							return;
						}
						break;
					case UNTIL_NOT:
						
						this._task.call(null);
						
						if (!this._stopped && !this._condition()) {
							this.stop();
							return;
						}
						break;
					default:
						this._task.call(null);
						break;
				}
				
			}finally {
				
			}
		}
		
		private function timerCompleteHandler(event:TimerEvent):void {
			try {
				this.stop();
			}finally {
				this.dispatchEvent(event);
			}
		}
		
		//----------------------------------------------------------------
		// Protected methods
		//----------------------------------------------------------------
		
		protected function stopped():void {
			this._condition = null;
		}
		
	}

}
